﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using static TaskQueueDemo.Logger;

namespace TaskQueueDemo
{
    public interface IQueueManager<T>
     where T : class, IQueueInfo<T>
    {
        void AddQueue(T q);

        T GetCurrent();

        void Start(Action<T> doJobAction, int oneTimeCount);

        event Action OnEnd;
    }
    /// <summary>
    /// 队列调度管理器
    /// 
    /// 负责将满足要求的队列进行消费
    /// 
    /// 满足要求是指队列处于未消费状态，且任务数量大于0，此时使用GetCurrent获取队列
    /// 
    /// 对于更进一步的需要，要求被消费的队列为剩余任务最多的队列，此时使用GetCurrentByLoop获取队列
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public class QueueManager<T>
        where T : IQueueInfo<T>
    {
        public QueueManager(int taskCount)
        {
            _tCount = taskCount;
        }

        private int _tCount;
        private object _lockObj = new object();
        private byte qIndex = 0;
        private List<T> qList = new List<T>();
        public void AddQueue(T q)
        {
            if (isRunning)
            {
                throw new Exception("isRunning，此时不能继续添加队列");
            }
            qList.Add(q);
        }

        private bool isRunning;

        /// <summary>
        /// 获得下一个 可以被消费的队列,如果找不到，则返回null
        /// 
        /// 此方法逻辑较为复杂
        /// 
        /// 需要考虑以下问题:
        /// 
        /// 在GetNext方法中，可以找到以当前p为起点，向后找到第一个满足条件的队列(非运行状态，且队列不为空)
        /// 如果向后找了一圈，又找回自己，发现都没有符合要求的队列，则将自己返回
        /// 
        /// 这时返回自己有两种可能，
        /// 1.自己队列中还有任务，此时会继续消费自己队列中的任务
        /// 2.自己队列中没有任务了，此时说明找了一圈都没有其他队列满足要求，且自己队列也消费空了，说明当前线程的所有功能都已完成，可以结束此线程的工作
        /// 因此，将自己这个空队列返回，外部判断返回的队列为空，跳出while循环，释放线程
        /// 
        /// 当上述第2种情况发生时，说明线程数已经大于可用的队列数，此时Current中保存的是空队列
        /// 因此，while (Queue_Can_Not_Be_Next(p)) 这个循环就是为了在这种情况下，再次执行GetNext方法，判断是否还有其他可用队列，这里很关键
        /// 
        /// 如果没有这个循环，则会造成 直接返回一个空队列，然后结束线程，但还有其他队列中有任务无法被消费
        /// 
        /// 上述问题在以下情况会发生:
        /// 3个队列，2个线程
        /// 1队列执行很慢
        /// 2，3队列执行很快
        /// 
        /// 所以线程2在2,3队列中交替执行，最终将2,3队列中的任务消费完成，此时Current是3队列，
        /// 当1队列执行完，调用GetCurrent，此时应该继续返回1队列，但如果不加 while (Queue_Can_Not_Be_Next(p))，则会返回队列3，造成线程提前结束
        /// 1队列还有剩余任务无法完成
        /// </summary>
        /// <returns></returns>
        private T GetCurrent()
        {
            lock (_lockObj)
            {
                // 设置之前
                var p = qList[qIndex];
                int startIndex = qIndex;

                // 检查当前指向的队列是否满足要求，如果不满足，就先后继续找
                while (Queue_Can_Not_Be_Next(p))
                {
                    MoveNextIndex();
                    p = qList[qIndex];
                    // 找了一圈，则返回自己
                    if (qIndex == startIndex)
                    {
                        if (Queue_Can_Not_Be_Next(p))
                        {
                            Log($"GetCurrent:找了一圈返回，返回null");
                            return default;
                        }
                        else
                        {
                            Log($"GetCurrent:找了一圈返回{p.Name}");
                            p.IsRunning = true;
                            return p;
                        }
                    }
                }

                // 向后移动指针,当下次调用GetCurrent时，默认从下一个位置开始查找队列
                MoveNextIndex();

                Log($"GetCurrent:返回值为{p.Name}");
                p.IsRunning = true;
                return p;
            }
        }

        /// <summary>
        /// 通过循环的方式，遍历所有队列，找出任务数最多且处于非运行状态的队列
        /// 
        /// 此方法相对于 上面的查找方式，保证了每次返回的队列都是最需要被消费的
        /// 
        /// 此方法的劣势为，当总队列数较多时，每次都遍历一遍，可能会影响性能，但这个一般可以忽略不计
        /// </summary>
        /// <returns></returns>
        private T GetCurrentByLoop()
        {
            lock (_lockObj)
            {
                T p = default;
                foreach (var item in qList)
                {
                    if (!item.IsRunning)
                    {
                        if (p == null)
                        {
                            if (item.JobCount > 0)
                            {
                                p = item;
                            }
                        }
                        else
                        {
                            if (item.JobCount > p.JobCount)
                            {
                                p = item;
                            }
                        }
                    }
                }

                if (p != null)
                {
                    p.IsRunning = true;
                }
                return p;
            }
        }

        /// <summary>
        /// 此队列是否可以成为下一个 待开始的队列
        /// </summary>
        /// <param name="next"></param>
        /// <returns></returns>
        private bool Queue_Can_Not_Be_Next(T next)
        {
            return next.IsRunning || next.JobCount == 0;
        }

        /// <summary>
        /// 从List集合中获取下一个对象，此方法封装了 循环逻辑
        /// </summary>
        /// <returns></returns>
        private void MoveNextIndex()
        {
            if (qIndex == qList.Count - 1)
            {
                qIndex = 0;
            }
            else
            {
                qIndex++;
            }
        }

        private List<Task> tasks = new List<Task>();
        /// <summary>
        /// 开启指定的线程数量，执行队列中的任务
        /// </summary>
        /// <param name="oneTimeCount">单次操作最多允许消费多个任务，通过此属性可以设置 消费均衡度，此值设置的越大，消费越不均衡</param>
        /// <param name="doJobAction"></param>
        public void Start(Action<T> doJobAction, int oneTimeCount)
        {
            isRunning = true;
            TaskFactory tf = new TaskFactory(TaskCreationOptions.LongRunning, TaskContinuationOptions.None);
            tasks.Clear();
            for (int i = 0; i < _tCount; i++)
            {
                tasks.Add(tf.StartNew(() =>
                {
                    // 首次执行获取队列
                    var q = GetCurrentByLoop();

                    while (q != null && q.JobCount > 0)
                    {
                        // 消费队列中的任务
                        doJobAction(q);
                        // 设置队列处于非运行状态
                        q.IsRunning = false;
                        // 获取下一个队列
                        q = GetCurrentByLoop();
                    }

                    Log($"TID:{Thread.CurrentThread.ManagedThreadId} is over");
                }));
            }

            // 所有任务都结束后，允许事件
            Task.WhenAll(tasks).ContinueWith(t =>
            {
                OnEnd?.Invoke();
                foreach (var item in qList)
                {
                    Console.WriteLine($"{item.Name}:IsRunning={item.IsRunning}");
                }
                qList.Clear();
                isRunning = false;
            });
        }

        public event Action OnEnd;
    }
}
